package cn.bonoon.kernel.util;

import java.beans.Introspector;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import javassist.ClassClassPath;
import javassist.ClassPool;

public final class ReflectHelper {

	private ReflectHelper() {
	}
	public static boolean isPublic(Class<?> clazz, Member member) {
		return Modifier.isPublic( member.getModifiers() ) && Modifier.isPublic( clazz.getModifiers() );
	}
	public static Method getGetterOrNull(Class<?> theClass, String propertyName) {
		return getGetterOrNull(theClass, propertyName, null);
	}
	
	public static Method getGetterOrNull(Class<?> theClass, String propertyName, Class<?> argType) {
		if (theClass==Object.class || theClass==null) {
			return null;
		}

		Method method = getGetter(theClass, propertyName);

		if ( method != null ) {
			if(null != argType && argType != method.getReturnType()) return null;
			if ( !isPublic(theClass, method) ) {
				method.setAccessible(true);
			}
			return method;
		}
		else {
			Method getter = getGetterOrNull( theClass.getSuperclass(), propertyName );
			if ( getter == null ) {
				Class<?>[] interfaces = theClass.getInterfaces();
				for ( int i=0; getter==null && i<interfaces.length; i++ ) {
					getter=getGetterOrNull( interfaces[i], propertyName );
				}
			}
			return getter;
		}
	}
	
	private static Method getGetter(Class<?> clazz, String propertyName){
		Method[] methods = clazz.getDeclaredMethods();
		for ( Method method : methods ) {
			// if the method has parameters, skip it
			if ( method.getParameterTypes().length != 0 ) {
				continue;
			}
			// if the method is a "bridge", skip it
			if ( method.isBridge() ) {
				continue;
			}

			final String methodName = method.getName();

			// try "get"
			if ( methodName.startsWith( "get" ) ) {
				String testStdMethod = Introspector.decapitalize( methodName.substring( 3 ) );
				String testOldMethod = methodName.substring( 3 );
				if ( testStdMethod.equals( propertyName ) || testOldMethod.equals( propertyName ) ) {
					return method;
				}
			}

			// if not "get", then try "is"
			if ( methodName.startsWith( "is" ) ) {
				String testStdMethod = Introspector.decapitalize( methodName.substring( 2 ) );
				String testOldMethod = methodName.substring( 2 );
				if ( testStdMethod.equals( propertyName ) || testOldMethod.equals( propertyName ) ) {
					return method;
				}
			}
		}
		return null;		
	}


	public static Method getSetterOrNull(Class<?> theClass, String propertyName, Class<?> returnType) {

		if (theClass==Object.class || theClass==null) return null;

		Method method = setterMethod(theClass, propertyName, returnType);

		if (method!=null) {
			if ( !isPublic(theClass, method) ) method.setAccessible(true);
			return method;
		}
		else {
			Method setter = getSetterOrNull( theClass.getSuperclass(), propertyName, returnType );
			if (setter==null) {
				Class<?>[] interfaces = theClass.getInterfaces();
				for ( int i=0; setter==null && i<interfaces.length; i++ ) {
					setter=getSetterOrNull( interfaces[i], propertyName, returnType );
				}
			}
			return setter;
		}

	}

	private static Method setterMethod(Class<?> theClass, String propertyName, Class<?> returnType) {

		Method[] methods = theClass.getDeclaredMethods();
		Method potentialSetter = null;
		for ( Method method : methods ) {
			final String methodName = method.getName();

			if ( method.getParameterTypes().length == 1 && methodName.startsWith( "set" ) ) {
				String testStdMethod = Introspector.decapitalize( methodName.substring( 3 ) );
				String testOldMethod = methodName.substring( 3 );
				if ( testStdMethod.equals( propertyName ) || testOldMethod.equals( propertyName ) ) {
					potentialSetter = method;
					if ( returnType == null || method.getParameterTypes()[0].equals( returnType ) ) {
						return potentialSetter;
					}
				}
			}
		}
		return potentialSetter;
	}
	private static ClassPool classPool;

	public static ClassPool getClassPool() {
		if (null == classPool) {
			classPool = new ClassPool(true);
			classPool.insertClassPath(new ClassClassPath(ReflectHelper.class));
		}
		return classPool;
	}

}
